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