openvswitch: Remove Linux bridge compatibility.
[sliver-openvswitch.git] / utilities / bugtool / ovs-bugtool.in
1 #! @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, 2012 Nicira, Inc.
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 OS_RELEASE = platform.release()
62
63 #
64 # Files & directories
65 #
66
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
71 GRUB_CONFIG = '/boot/grub/menu.lst'
72 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
73 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
74 PROC_PARTITIONS = '/proc/partitions'
75 FSTAB = '/etc/fstab'
76 PROC_MOUNTS = '/proc/mounts'
77 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
78 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
79 LVM_CACHE = '/etc/lvm/cache/.cache'
80 LVM_CONFIG = '/etc/lvm/lvm.conf'
81 PROC_CPUINFO = '/proc/cpuinfo'
82 PROC_MEMINFO = '/proc/meminfo'
83 PROC_IOPORTS = '/proc/ioports'
84 PROC_INTERRUPTS = '/proc/interrupts'
85 PROC_SCSI = '/proc/scsi/scsi'
86 PROC_VERSION = '/proc/version'
87 PROC_MODULES = '/proc/modules'
88 PROC_DEVICES = '/proc/devices'
89 PROC_FILESYSTEMS = '/proc/filesystems'
90 PROC_CMDLINE = '/proc/cmdline'
91 PROC_CONFIG = '/proc/config.gz'
92 PROC_USB_DEV = '/proc/bus/usb/devices'
93 PROC_XEN_BALLOON = '/proc/xen/balloon'
94 PROC_NET_BONDING_DIR = '/proc/net/bonding'
95 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
96 ROUTE_RE = re.compile(r'^.*/route-.*')
97 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
98 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
99 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
100 PROC_NET_VLAN_DIR = '/proc/net/vlan'
101 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
102 MODPROBE_CONF = '/etc/modprobe.conf'
103 MODPROBE_DIR = '/etc/modprobe.d'
104 RESOLV_CONF = '/etc/resolv.conf'
105 MPP_CONF = '/etc/mpp.conf'
106 MULTIPATH_CONF = '/etc/multipath.conf'
107 NSSWITCH_CONF = '/etc/nsswitch.conf'
108 NTP_CONF = '/etc/ntp.conf'
109 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
110 HOSTS = '/etc/hosts'
111 HOSTS_ALLOW = '/etc/hosts.allow'
112 HOSTS_DENY = '/etc/hosts.deny'
113 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
114 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
115 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
116 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch'    # RHEL
117 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
118 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
119 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
120 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
121 VAR_LOG_DIR = '/var/log/'
122 VAR_LOG_CORE_DIR = '/var/log/core'
123 X11_LOGS_DIR = VAR_LOG_DIR
124 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
125 X11_AUTH_DIR = '/root/'
126 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
127 YUM_LOG = '/var/log/yum.log'
128 YUM_REPOS_DIR = '/etc/yum.repos.d'
129 PAM_DIR = '/etc/pam.d'
130 KRB5_CONF = '/etc/krb5.conf'
131
132 #
133 # External programs
134 #
135
136 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
137 ARP = 'arp'
138 CAT = 'cat'
139 CHKCONFIG = 'chkconfig'
140 DF = 'df'
141 DMESG = 'dmesg'
142 DMIDECODE = 'dmidecode'
143 DMSETUP = 'dmsetup'
144 DPKG_QUERY = 'dpkg-query'
145 ETHTOOL = 'ethtool'
146 FDISK = 'fdisk'
147 FIND = 'find'
148 HDPARM = 'hdparm'
149 IFCONFIG = 'ifconfig'
150 IPTABLES = 'iptables'
151 ISCSIADM = 'iscsiadm'
152 LOSETUP = 'losetup'
153 LS = 'ls'
154 LSPCI = 'lspci'
155 LVDISPLAY = 'lvdisplay'
156 LVS = 'lvs'
157 MD5SUM = 'md5sum'
158 MODINFO = 'modinfo'
159 MPPUTIL = 'mppUtil'
160 MULTIPATHD = 'multipathd'
161 NETSTAT = 'netstat'
162 OVS_DPCTL = 'ovs-dpctl'
163 OVS_OFCTL = 'ovs-ofctl'
164 OVS_VSCTL = 'ovs-vsctl'
165 OVS_APPCTL = 'ovs-appctl'
166 PS = 'ps'
167 PVS = 'pvs'
168 ROUTE = 'route'
169 RPM = 'rpm'
170 SG_MAP = 'sg_map'
171 SYSCTL = 'sysctl'
172 TC = 'tc'
173 UPTIME = 'uptime'
174 VGS = 'vgs'
175 VGSCAN = 'vgscan'
176 ZCAT = 'zcat'
177
178 #
179 # PII -- Personally identifiable information.  Of particular concern are
180 # things that would identify customers, or their network topology.
181 # Passwords are never to be included in any bug report, regardless of any PII
182 # declaration.
183 #
184 # NO            -- No PII will be in these entries.
185 # YES           -- PII will likely or certainly be in these entries.
186 # MAYBE         -- The user may wish to audit these entries for PII.
187 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
188 # but since we encourage customers to edit these files, PII may have been
189 # introduced by the customer.  This is used in particular for the networking
190 # scripts in dom0.
191 #
192
193 PII_NO            = 'no'
194 PII_YES           = 'yes'
195 PII_MAYBE         = 'maybe'
196 PII_IF_CUSTOMIZED = 'if_customized'
197 KEY      = 0
198 PII      = 1
199 MIN_SIZE = 2
200 MAX_SIZE = 3
201 MIN_TIME = 4
202 MAX_TIME = 5
203 MIME     = 6
204 CHECKED  = 7
205 HIDDEN   = 8
206
207 MIME_DATA = 'application/data'
208 MIME_TEXT = 'text/plain'
209
210 INVENTORY_XML_ROOT = "system-status-inventory"
211 INVENTORY_XML_SUMMARY = 'system-summary'
212 INVENTORY_XML_ELEMENT = 'inventory-entry'
213 CAP_XML_ROOT = "system-status-capabilities"
214 CAP_XML_ELEMENT = 'capability'
215
216
217 CAP_BLOBS                = 'blobs'
218 CAP_BOOT_LOADER          = 'boot-loader'
219 CAP_COLLECTD_LOGS        = 'collectd-logs'
220 CAP_DISK_INFO            = 'disk-info'
221 CAP_FIRSTBOOT            = 'firstboot'
222 CAP_HARDWARE_INFO        = 'hardware-info'
223 CAP_HDPARM_T             = 'hdparm-t'
224 CAP_HIGH_AVAILABILITY    = 'high-availability'
225 CAP_KERNEL_INFO          = 'kernel-info'
226 CAP_LOSETUP_A            = 'loopback-devices'
227 CAP_MULTIPATH            = 'multipath'
228 CAP_NETWORK_CONFIG       = 'network-config'
229 CAP_NETWORK_STATUS       = 'network-status'
230 CAP_OEM                  = 'oem'
231 CAP_PAM                  = 'pam'
232 CAP_PROCESS_LIST         = 'process-list'
233 CAP_PERSISTENT_STATS     = 'persistent-stats'
234 CAP_SYSTEM_LOGS          = 'system-logs'
235 CAP_SYSTEM_SERVICES      = 'system-services'
236 CAP_VNCTERM              = 'vncterm'
237 CAP_WLB                  = 'wlb'
238 CAP_X11_LOGS             = 'X11'
239 CAP_X11_AUTH             = 'X11-auth'
240 CAP_YUM                  = 'yum'
241
242 KB = 1024
243 MB = 1024 * 1024
244
245 caps = {}
246 cap_sizes = {}
247 unlimited_data = False
248 dbg = False
249
250 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
251         max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
252     caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
253                  checked, hidden)
254     cap_sizes[key] = 0
255
256
257 cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
258 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
259     max_time=5)
260 cap(CAP_COLLECTD_LOGS,       PII_MAYBE,                 max_size=50*MB,
261     max_time=5)
262 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
263     max_time=20)
264 cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
265 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
266     max_time=20)
267 cap(CAP_HDPARM_T,            PII_NO,    min_size=0,     max_size=5*KB,
268     min_time=20, max_time=90, checked=False, hidden=True)
269 cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
270 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
271     max_time=5)
272 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
273 cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
274     max_time=10)
275 cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
276                                         min_size=0,     max_size=40*KB)
277 cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=50*KB,
278     max_time=30)
279 cap(CAP_PAM,                 PII_NO,                    max_size=50*KB)
280 cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
281     max_time=60)
282 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
283     max_time=20)
284 cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=50*MB,
285     max_time=5)
286 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
287     max_time=20)
288 cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
289 cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
290     max_time=20)
291 cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
292 cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
293 cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
294     max_time=30)
295
296 ANSWER_YES_TO_ALL = False
297 SILENT_MODE = False
298 entries = None
299 data = {}
300 dev_null = open('/dev/null', 'r+')
301
302 def output(x):
303     global SILENT_MODE
304     if not SILENT_MODE:
305         print x
306
307 def output_ts(x):
308     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
309
310 def cmd_output(cap, args, label=None, filter=None):
311     if cap in entries:
312         if not label:
313             if isinstance(args, list):
314                 a = [aa for aa in args]
315                 a[0] = os.path.basename(a[0])
316                 label = ' '.join(a)
317             else:
318                 label = args
319         data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
320
321 def file_output(cap, path_list, newest_first=False):
322     """
323     If newest_first is True, the list of files in path_list is sorted
324     by file modification time in descending order, else its sorted
325     in ascending order.
326     """
327     if cap in entries:
328         path_entries = []
329         for path in path_list:
330             try:
331                 s = os.stat(path)
332             except OSError, e:
333                 continue
334             path_entries.append((path, s))
335
336         mtime = lambda(path, stat): stat.st_mtime
337         path_entries.sort(key=mtime, reverse=newest_first)
338         for p in path_entries:
339             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
340                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
341                 data[p] = {'cap': cap, 'filename': p[0]}
342                 cap_sizes[cap] += p[1].st_size
343             else:
344                 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
345
346 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
347     """
348     Walks the directory tree rooted at path. Files in current dir are processed
349     before files in sub-dirs.
350     """
351     if cap in entries:
352         if os.path.exists(path):
353             for root, dirs, files in os.walk(path):
354                 fns = [fn for fn in [os.path.join(root, f) for f in files]
355                        if os.path.isfile(fn) and matches(fn, pattern, negate)]
356                 file_output(cap, fns, newest_first=newest_first)
357
358 def func_output(cap, label, func):
359     if cap in entries:
360         t = str(func).split()
361         data[label] = {'cap': cap, 'func': func}
362
363 def collect_data():
364     process_lists = {}
365
366     for (k, v) in data.items():
367         cap = v['cap']
368         if v.has_key('cmd_args'):
369             v['output'] = StringIOmtime()
370             if not process_lists.has_key(cap):
371                 process_lists[cap] = []
372             process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
373         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
374             # proc files must be read into memory
375             try:
376                 f = open(v['filename'], 'r')
377                 s = f.read()
378                 f.close()
379                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
380                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
381                     v['output'] = StringIOmtime(s)
382                     cap_sizes[cap] += len(s)
383                 else:
384                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
385             except:
386                 pass
387         elif v.has_key('func'):
388             try:
389                 s = v['func'](cap)
390             except Exception, e:
391                 s = str(e)
392             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
393                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
394                 v['output'] = StringIOmtime(s)
395                 cap_sizes[cap] += len(s)
396             else:
397                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
398
399     run_procs(process_lists.values())
400
401
402 def main(argv=None):
403     global ANSWER_YES_TO_ALL, SILENT_MODE
404     global entries, data, dbg, unlimited_data
405
406     # Filter flags
407     only_ovs_info = False
408     collect_all_info = True
409
410     # we need access to privileged files, exit if we are not running as root
411     if os.getuid() != 0:
412         print >>sys.stderr, "Error: ovs-bugtool must be run as root"
413         return 1
414
415     output_file = None
416     output_type = 'tar.bz2'
417     output_fd = -1
418
419     if argv is None:
420         argv = sys.argv
421
422     try:
423         (options, params) = getopt.gnu_getopt(
424             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
425                          'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
426                          'debug', 'ovs'])
427     except getopt.GetoptError, opterr:
428         print >>sys.stderr, opterr
429         return 2
430
431     try:
432         load_plugins(True)
433     except:
434         pass
435
436     entries = [e for e in caps.keys() if caps[e][CHECKED]]
437
438     for (k, v) in options:
439         if k == '--capabilities':
440             update_capabilities()
441             print_capabilities()
442             return 0
443
444         if k == '--output':
445             if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
446                 output_type = v
447             else:
448                 print >>sys.stderr, "Invalid output format '%s'" % v
449                 return 2
450
451         # "-s" or "--silent" means suppress output (except for the final
452         # output filename at the end)
453         if k in ['-s', '--silent']:
454             SILENT_MODE = True
455
456         if k == '--entries' and v != '':
457             entries = v.split(',')
458
459         # If the user runs the script with "-y" or "--yestoall" we don't ask
460         # all the really annoying questions.
461         if k in ['-y', '--yestoall']:
462             ANSWER_YES_TO_ALL = True
463
464         if k == '--outfd':
465             output_fd = int(v)
466             try:
467                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
468                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
469             except:
470                 print >>sys.stderr, "Invalid output file descriptor", output_fd
471                 return 2
472
473         if k == '--outfile':
474             output_file = v
475
476         elif k == '--all':
477             entries = caps.keys()
478         elif k == '--unlimited':
479             unlimited_data = True
480         elif k == '--debug':
481             dbg = True
482             ProcOutput.debug = True
483
484         if k == '--ovs':
485             only_ovs_info = True
486             collect_all_info = False
487
488     if len(params) != 1:
489         print >>sys.stderr, "Invalid additional arguments", str(params)
490         return 2
491
492     if output_fd != -1 and output_type != 'tar':
493         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
494         return 2
495
496     if output_fd != -1 and output_file is not None:
497         print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
498         return 2
499
500     if ANSWER_YES_TO_ALL:
501         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
502
503     output('''
504 This application will collate dmesg output, details of the
505 hardware configuration of your machine, information about the build of
506 openvswitch that you are using, plus, if you allow it, various logs.
507
508 The collated information will be saved as a .%s for archiving or
509 sending to a Technical Support Representative.
510
511 The logs may contain private information, and if you are at all
512 worried about that, you should exit now, or you should explicitly
513 exclude those logs from the archive.
514
515 ''' % output_type)
516
517     # assemble potential data
518
519     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
520     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
521     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
522
523     tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
524     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
525     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
526     file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
527     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
528     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
529     for d in disk_list():
530         cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
531     if len(pidof('iscsid')) != 0:
532         cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
533     cmd_output(CAP_DISK_INFO, [VGSCAN])
534     cmd_output(CAP_DISK_INFO, [PVS])
535     cmd_output(CAP_DISK_INFO, [VGS])
536     cmd_output(CAP_DISK_INFO, [LVS])
537     file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
538     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
539     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
540     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
541     cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
542     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
543     cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
544
545     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
546     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
547     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
548     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
549     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
550     file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
551     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
552     # FIXME IDE?
553
554     for d in disk_list():
555         cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
556
557     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
558                                   PROC_FILESYSTEMS, PROC_CMDLINE])
559     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
560     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
561     file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
562     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
563     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
564
565     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
566
567     file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
568     cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
569     func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
570     cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
571     if CAP_MULTIPATH in entries and collect_all_info:
572         dump_rdac_groups(CAP_MULTIPATH)
573
574     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
575     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
576     file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
577     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
578     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
579
580     cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
581     cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
582     cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
583     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
584     for dir in DHCP_LEASE_DIR:
585         tree_output(CAP_NETWORK_STATUS, dir)
586     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
587     for p in os.listdir('/sys/class/net/'):
588         try:
589             f = open('/sys/class/net/%s/type' % p, 'r')
590             t = f.readline()
591             f.close()
592             if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
593                 # ARPHRD_ETHER
594                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
595                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
596                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
597                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
598                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
599             if int(t) == 1:
600                 cmd_output(CAP_NETWORK_STATUS,
601                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
602         except:
603             pass
604     tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
605     tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
606     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
607     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
608     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
609         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
610         for d in dp_list():
611             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
612             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
613             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
614         try:
615             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
616             vspid = int(vspidfile.readline().strip())
617             vspidfile.close()
618             for b in bond_list(vspid):
619                 cmd_output(CAP_NETWORK_STATUS,
620                            [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
621                            'ovs-appctl-bond-show-%s.out' % b)
622         except e:
623             pass
624
625     tree_output(CAP_PAM, PAM_DIR)
626     file_output(CAP_PAM, [KRB5_CONF])
627
628     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
629     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
630
631     logs = ([ VAR_LOG_DIR + x for x in
632              [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
633              'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
634             + [ OPENVSWITCH_LOG_DIR + x for x in
635                 [ 'ovs-vswitchd.log', 'ovsdb-server.log',
636                   'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
637     file_output(CAP_SYSTEM_LOGS, logs)
638     file_output(CAP_SYSTEM_LOGS,
639                 [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
640     file_output(CAP_SYSTEM_LOGS,
641                 [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
642
643     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
644         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
645
646     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
647
648     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
649     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
650     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
651
652     file_output(CAP_YUM, [YUM_LOG])
653     tree_output(CAP_YUM, YUM_REPOS_DIR)
654     cmd_output(CAP_YUM, [RPM, '-qa'])
655     file_output(CAP_YUM, [APT_SOURCES_LIST])
656     tree_output(CAP_YUM, APT_SOURCES_LIST_D)
657     cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
658
659     # Filter out ovs relevant information if --ovs option passed
660     # else collect all information
661     filters = set()
662     if only_ovs_info:
663         filters.add('ovs')
664         ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
665                          CAP_NETWORK_CONFIG]
666         ovs_info_list = ['process-tree']
667         # We cannot use iteritems, since we modify 'data' as we pass through
668         for (k, v) in data.items():
669             cap = v['cap']
670             if 'filename' in v:
671                 info = k[0]
672             else:
673                 info = k
674             if info not in ovs_info_list and cap not in ovs_info_caps:
675                 del data[k]
676
677     if filters:
678         filter = ",".join(filters)
679     else:
680         filter = None
681
682     try:
683         load_plugins(filter=filter)
684     except:
685         pass
686
687     # permit the user to filter out data
688     # We cannot use iteritems, since we modify 'data' as we pass through
689     for (k, v) in sorted(data.items()):
690        cap = v['cap']
691        if 'filename' in v:
692            key = k[0]
693        else:
694            key = k
695        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
696            del data[k]
697
698     # collect selected data now
699     output_ts('Running commands to collect data')
700     collect_data()
701
702     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
703
704     # include inventory
705     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
706
707     # create archive
708     if output_fd == -1:
709         if output_file is None:
710             dirname = BUG_DIR
711         else:
712             dirname = os.path.dirname(output_file)
713         if dirname and not os.path.exists(dirname):
714             try:
715                 os.makedirs(dirname)
716             except:
717                 pass
718
719     if output_fd == -1:
720         output_ts('Creating output file')
721
722     if output_type.startswith('tar'):
723         make_tar(subdir, output_type, output_fd, output_file)
724     else:
725         make_zip(subdir, output_file)
726
727     clean_tapdisk_logs()
728
729     if dbg:
730         print >>sys.stderr, "Category sizes (max, actual):\n"
731         for c in caps.keys():
732             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
733                                                      cap_sizes[c])
734     return 0
735
736 def find_tapdisk_logs():
737     return glob.glob('/var/log/blktap/*.log*')
738
739 def generate_tapdisk_logs():
740     for pid in pidof('tapdisk'):
741         try:
742             os.kill(pid, SIGUSR1)
743             output_ts("Including logs for tapdisk process %d" % pid)
744         except :
745             pass
746     # give processes a second to write their logs
747     time.sleep(1)
748
749 def clean_tapdisk_logs():
750     for filename in find_tapdisk_logs():
751         try:
752             os.remove(filename)
753         except :
754             pass
755
756 def filter_db_pii(str, state):
757     if 'in_secret_table' not in state:
758         state['in_secret_table'] = False
759
760     if str.startswith('<table ') and 'name="secret"' in str:
761         state['in_secret_table'] = True
762     elif str.startswith('</table>'):
763         state['in_secret_table'] = False
764
765     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
766         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
767     return str
768
769 def dump_scsi_hosts(cap):
770     output = ''
771     l = os.listdir('/sys/class/scsi_host')
772     l.sort()
773
774     for h in l:
775         procname = ''
776         try:
777                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
778                 procname = f.readline().strip("\n")
779                 f.close()
780         except:
781                 pass
782         modelname = None
783         try:
784                 f = open('/sys/class/scsi_host/%s/model_name' % h)
785                 modelname = f.readline().strip("\n")
786                 f.close()
787         except:
788                 pass
789
790         output += "%s:\n" %h
791         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
792
793     return output
794
795 def module_info(cap):
796     output = StringIO.StringIO()
797     modules = open(PROC_MODULES, 'r')
798     procs = []
799
800     for line in modules:
801         module = line.split()[0]
802         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
803     modules.close()
804
805     run_procs([procs])
806
807     return output.getvalue()
808
809
810 def multipathd_topology(cap):
811     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
812                      stdout=PIPE, stderr=dev_null)
813     stdout, stderr = pipe.communicate('show topology')
814
815     return stdout
816
817 def dp_list():
818     output = StringIO.StringIO()
819     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
820
821     run_procs([procs])
822
823     if not procs[0].timed_out:
824         return output.getvalue().splitlines()
825     return []
826
827 def bond_list(pid):
828     output = StringIO.StringIO()
829     procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
830
831     run_procs([procs])
832
833     if not procs[0].timed_out:
834         bonds = output.getvalue().splitlines()[1:]
835         return [x.split('\t')[1] for x in bonds]
836     return []
837
838 def fd_usage(cap):
839     output = ''
840     fd_dict = {}
841     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
842         try:
843             fh = open('/proc/'+d+'/cmdline')
844             name = fh.readline()
845             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
846             if num_fds > 0:
847                 if not num_fds in fd_dict:
848                     fd_dict[num_fds] = []
849                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
850         finally:
851             fh.close()
852     keys = fd_dict.keys()
853     keys.sort(lambda a, b: int(b) - int(a))
854     for k in keys:
855         output += "%s: %s\n" % (k, str(fd_dict[k]))
856     return output
857
858 def dump_rdac_groups(cap):
859     output = StringIO.StringIO()
860     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
861
862     run_procs([procs])
863
864     if not procs[0].timed_out:
865         proc_line = 0
866         for line in output.getvalue().splitlines():
867             if line.startswith('ID'):
868                 proc_line = 2
869             elif line.startswith('----'):
870                 proc_line -= 1
871             elif proc_line > 0:
872                 group, _ = line.split(None, 1)
873                 cmd_output(cap, [MPPUTIL, '-g', group])
874
875 def load_plugins(just_capabilities=False, filter=None):
876     def getText(nodelist):
877         rc = ""
878         for node in nodelist:
879             if node.nodeType == node.TEXT_NODE:
880                 rc += node.data
881         return rc.encode()
882
883     def getBoolAttr(el, attr, default=False):
884         ret = default
885         val = el.getAttribute(attr).lower()
886         if val in ['true', 'false', 'yes', 'no']:
887             ret = val in ['true', 'yes']
888         return ret
889
890     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
891         if not caps.has_key(dir):
892             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
893                 continue
894             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
895             assert xmldoc.documentElement.tagName == "capability"
896
897             pii, min_size, max_size, min_time, max_time, mime = \
898                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
899
900             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
901                 pii = xmldoc.documentElement.getAttribute("pii")
902             if xmldoc.documentElement.getAttribute("min_size") != '':
903                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
904             if xmldoc.documentElement.getAttribute("max_size") != '':
905                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
906             if xmldoc.documentElement.getAttribute("min_time") != '':
907                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
908             if xmldoc.documentElement.getAttribute("max_time") != '':
909                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
910             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
911                 mime = xmldoc.documentElement.getAttribute("mime")
912             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
913             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
914
915             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
916
917         if just_capabilities:
918             continue
919
920         plugdir = os.path.join(PLUGIN_DIR, dir)
921         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
922             xmldoc = parse(os.path.join(plugdir, file))
923             assert xmldoc.documentElement.tagName == "collect"
924
925             for el in xmldoc.documentElement.getElementsByTagName("*"):
926                 filters_tmp = el.getAttribute("filters")
927                 if filters_tmp == '':
928                     filters = []
929                 else:
930                     filters = filters_tmp.split(',')
931                 if not(filter is None or filter in filters):
932                     continue
933                 if el.tagName == "files":
934                     newest_first = getBoolAttr(el, 'newest_first')
935                     file_output(dir, getText(el.childNodes).split(),
936                                 newest_first=newest_first)
937                 elif el.tagName == "directory":
938                     pattern = el.getAttribute("pattern")
939                     if pattern == '': pattern = None
940                     negate = getBoolAttr(el, 'negate')
941                     newest_first = getBoolAttr(el, 'newest_first')
942                     tree_output(dir, getText(el.childNodes),
943                                 pattern and re.compile(pattern) or None,
944                                 negate=negate, newest_first=newest_first)
945                 elif el.tagName == "command":
946                     label = el.getAttribute("label")
947                     if label == '': label = None
948                     cmd_output(dir, getText(el.childNodes), label)
949
950 def make_tar(subdir, suffix, output_fd, output_file):
951     global SILENT_MODE, data
952
953     mode = 'w'
954     if suffix == 'tar.bz2':
955         mode = 'w:bz2'
956     elif suffix == 'tar.gz':
957         mode = 'w:gz'
958
959     if output_fd == -1:
960         if output_file is None:
961             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
962         else:
963             filename = output_file
964         old_umask = os.umask(0077)
965         tf = tarfile.open(filename, mode)
966         os.umask(old_umask)
967     else:
968         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
969
970     try:
971         for (k, v) in data.items():
972             try:
973                 tar_filename = os.path.join(subdir, construct_filename(k, v))
974                 ti = tarfile.TarInfo(tar_filename)
975
976                 ti.uname = 'root'
977                 ti.gname = 'root'
978
979                 if v.has_key('output'):
980                     ti.mtime = v['output'].mtime
981                     ti.size = len(v['output'].getvalue())
982                     v['output'].seek(0)
983                     tf.addfile(ti, v['output'])
984                 elif v.has_key('filename'):
985                     s = os.stat(v['filename'])
986                     ti.mtime = s.st_mtime
987                     ti.size = s.st_size
988                     tf.addfile(ti, file(v['filename']))
989             except:
990                 pass
991     finally:
992         tf.close()
993
994     if output_fd == -1:
995         output ('Writing tarball %s successful.' % filename)
996         if SILENT_MODE:
997             print filename
998
999
1000 def make_zip(subdir, output_file):
1001     global SILENT_MODE, data
1002
1003     if output_file is None:
1004         filename = "%s/%s.zip" % (BUG_DIR, subdir)
1005     else:
1006         filename = output_file
1007     old_umask = os.umask(0077)
1008     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1009     os.umask(old_umask)
1010
1011     try:
1012         for (k, v) in data.items():
1013             try:
1014                 dest = os.path.join(subdir, construct_filename(k, v))
1015
1016                 if v.has_key('output'):
1017                     zf.writestr(dest, v['output'].getvalue())
1018                 else:
1019                     if os.stat(v['filename']).st_size < 50:
1020                         compress_type = zipfile.ZIP_STORED
1021                     else:
1022                         compress_type = zipfile.ZIP_DEFLATED
1023                     zf.write(v['filename'], dest, compress_type)
1024             except:
1025                 pass
1026     finally:
1027         zf.close()
1028
1029     output ('Writing archive %s successful.' % filename)
1030     if SILENT_MODE:
1031         print filename
1032
1033
1034 def make_inventory(inventory, subdir):
1035     document = getDOMImplementation().createDocument(
1036         None, INVENTORY_XML_ROOT, None)
1037
1038     # create summary entry
1039     s = document.createElement(INVENTORY_XML_SUMMARY)
1040     user = os.getenv('SUDO_USER', os.getenv('USER'))
1041     if user:
1042         s.setAttribute('user', user)
1043     s.setAttribute('date', time.strftime('%c'))
1044     s.setAttribute('hostname', platform.node())
1045     s.setAttribute('uname', ' '.join(platform.uname()))
1046     s.setAttribute('uptime', commands.getoutput(UPTIME))
1047     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1048
1049     map(lambda (k, v): inventory_entry(document, subdir, k, v),
1050         inventory.items())
1051     return document.toprettyxml()
1052
1053 def inventory_entry(document, subdir, k, v):
1054     try:
1055         el = document.createElement(INVENTORY_XML_ELEMENT)
1056         el.setAttribute('capability', v['cap'])
1057         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1058         el.setAttribute('md5sum', md5sum(v))
1059         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1060     except:
1061         pass
1062
1063
1064 def md5sum(d):
1065     m = md5.new()
1066     if d.has_key('filename'):
1067         f = open(d['filename'])
1068         data = f.read(1024)
1069         while len(data) > 0:
1070             m.update(data)
1071             data = f.read(1024)
1072         f.close()
1073     elif d.has_key('output'):
1074         m.update(d['output'].getvalue())
1075     return m.hexdigest()
1076
1077
1078 def construct_filename(k, v):
1079     if v.has_key('filename'):
1080         if v['filename'][0] == '/':
1081             return v['filename'][1:]
1082         else:
1083             return v['filename']
1084     s = k.replace(' ', '-')
1085     s = s.replace('--', '-')
1086     s = s.replace('/', '%')
1087     if s.find('.') == -1:
1088         s += '.out'
1089
1090     return s
1091
1092 def update_capabilities():
1093     pass
1094
1095 def update_cap_size(cap, size):
1096     update_cap(cap, MIN_SIZE, size)
1097     update_cap(cap, MAX_SIZE, size)
1098     update_cap(cap, CHECKED, size > 0)
1099
1100
1101 def update_cap(cap, k, v):
1102     global caps
1103     l = list(caps[cap])
1104     l[k] = v
1105     caps[cap] = tuple(l)
1106
1107
1108 def size_of_dir(d, pattern=None, negate=False):
1109     if os.path.isdir(d):
1110         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1111                            pattern, negate)
1112     else:
1113         return 0
1114
1115
1116 def size_of_all(files, pattern=None, negate=False):
1117     return sum([size_of(f, pattern, negate) for f in files])
1118
1119
1120 def matches(f, pattern, negate):
1121     if negate:
1122         return not matches(f, pattern, False)
1123     else:
1124         return pattern is None or pattern.match(f)
1125
1126
1127 def size_of(f, pattern, negate):
1128     if os.path.isfile(f) and matches(f, pattern, negate):
1129         return os.stat(f)[6]
1130     else:
1131         return size_of_dir(f, pattern, negate)
1132
1133
1134 def print_capabilities():
1135     document = getDOMImplementation().createDocument(
1136         "ns", CAP_XML_ROOT, None)
1137     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1138     print document.toprettyxml()
1139
1140 def capability(document, key):
1141     c = caps[key]
1142     el = document.createElement(CAP_XML_ELEMENT)
1143     el.setAttribute('key', c[KEY])
1144     el.setAttribute('pii', c[PII])
1145     el.setAttribute('min-size', str(c[MIN_SIZE]))
1146     el.setAttribute('max-size', str(c[MAX_SIZE]))
1147     el.setAttribute('min-time', str(c[MIN_TIME]))
1148     el.setAttribute('max-time', str(c[MAX_TIME]))
1149     el.setAttribute('content-type', c[MIME])
1150     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1151     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1152
1153
1154 def prettyDict(d):
1155     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1156     return '\n'.join([format % i for i in d.items()]) + '\n'
1157
1158
1159 def yes(prompt):
1160     yn = raw_input(prompt)
1161
1162     return len(yn) == 0 or yn.lower()[0] == 'y'
1163
1164
1165 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1166
1167 def disk_list():
1168     disks = []
1169     try:
1170         f = open('/proc/partitions')
1171         f.readline()
1172         f.readline()
1173         for line in f.readlines():
1174             (major, minor, blocks, name) = line.split()
1175             if int(major) < 254 and not partition_re.match(name):
1176                 disks.append(name)
1177         f.close()
1178     except:
1179         pass
1180     return disks
1181
1182
1183 class ProcOutput:
1184     debug = False
1185
1186     def __init__(self, command, max_time, inst=None, filter=None):
1187         self.command = command
1188         self.max_time = max_time
1189         self.inst = inst
1190         self.running = False
1191         self.status = None
1192         self.timed_out = False
1193         self.failed = False
1194         self.timeout = int(time.time()) + self.max_time
1195         self.filter = filter
1196         self.filter_state = {}
1197
1198     def __del__(self):
1199         self.terminate()
1200
1201     def cmdAsStr(self):
1202         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1203
1204     def run(self):
1205         self.timed_out = False
1206         try:
1207             if ProcOutput.debug:
1208                 output_ts("Starting '%s'" % self.cmdAsStr())
1209             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1210             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1211             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1212             self.running = True
1213             self.failed = False
1214         except:
1215             output_ts("'%s' failed" % self.cmdAsStr())
1216             self.running = False
1217             self.failed = True
1218
1219     def terminate(self):
1220         if self.running:
1221             try:
1222                 self.proc.stdout.close()
1223                 os.kill(self.proc.pid, SIGTERM)
1224             except:
1225                 pass
1226             self.proc = None
1227             self.running = False
1228             self.status = SIGTERM
1229
1230     def read_line(self):
1231         assert self.running
1232         line = self.proc.stdout.readline()
1233         if line == '':
1234             # process exited
1235             self.proc.stdout.close()
1236             self.status = self.proc.wait()
1237             self.proc = None
1238             self.running = False
1239         else:
1240             if self.filter:
1241                 line = self.filter(line, self.filter_state)
1242             if self.inst:
1243                 self.inst.write(line)
1244
1245 def run_procs(procs):
1246     while True:
1247         pipes = []
1248         active_procs = []
1249
1250         for pp in procs:
1251             for p in pp:
1252                 if p.running:
1253                     active_procs.append(p)
1254                     pipes.append(p.proc.stdout)
1255                     break
1256                 elif p.status == None and not p.failed and not p.timed_out:
1257                     p.run()
1258                     if p.running:
1259                         active_procs.append(p)
1260                         pipes.append(p.proc.stdout)
1261                         break
1262
1263         if len(pipes) == 0:
1264             # all finished
1265             break
1266
1267         (i, o, x) = select(pipes, [], [], 1.0)
1268         now = int(time.time())
1269
1270         # handle process output
1271         for p in active_procs:
1272             if p.proc.stdout in i:
1273                 p.read_line()
1274
1275             # handle timeout
1276             if p.running and now > p.timeout:
1277                 output_ts("'%s' timed out" % p.cmdAsStr())
1278                 if p.inst:
1279                     p.inst.write("\n** timeout **\n")
1280                 p.timed_out = True
1281                 p.terminate()
1282
1283
1284 def pidof(name):
1285     pids = []
1286
1287     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1288         try:
1289             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1290                 pids.append(int(d))
1291         except:
1292             pass
1293
1294     return pids
1295
1296
1297 class StringIOmtime(StringIO.StringIO):
1298     def __init__(self, buf=''):
1299         StringIO.StringIO.__init__(self, buf)
1300         self.mtime = time.time()
1301
1302     def write(self, s):
1303         StringIO.StringIO.write(self, s)
1304         self.mtime = time.time()
1305
1306
1307 if __name__ == "__main__":
1308     try:
1309         sys.exit(main())
1310     except KeyboardInterrupt:
1311         print "\nInterrupted."
1312         sys.exit(3)