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