ovs-bugtool: Change the default output type.
[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*MB,
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.gz'
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, '-S', p])
604                 if not p.startswith('vif') and not p.startswith('tap'):
605                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
606                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
607                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
608                     cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
609             if int(t) == 1:
610                 cmd_output(CAP_NETWORK_STATUS,
611                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
612         except:
613             pass
614     tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
615     tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
616     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
617     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
618     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
619         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
620         for d in dp_list():
621             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
622             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
623             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
624         try:
625             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
626             vspid = int(vspidfile.readline().strip())
627             vspidfile.close()
628             for b in bond_list(vspid):
629                 cmd_output(CAP_NETWORK_STATUS,
630                            [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
631                            'ovs-appctl-bond-show-%s.out' % b)
632         except e:
633             pass
634
635     tree_output(CAP_PAM, PAM_DIR)
636     file_output(CAP_PAM, [KRB5_CONF])
637
638     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
639     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
640
641     system_logs = (CAP_SYSTEM_LOGS, [ VAR_LOG_DIR + x for x in
642         ['crit.log', 'kern.log', 'daemon.log', 'user.log',
643         'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
644     ovs_logs = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
645         ['ovs-vswitchd.log', 'ovsdb-server.log',
646         'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
647     for cap, logs in [system_logs, ovs_logs]:
648         file_output(cap, logs)
649         file_output(cap,
650             ['%s.%d' % (f, n) for n in range(log_days) for f in logs])
651         file_output(cap,
652             ['%s.%d.gz' % (f, n) for n in range(log_days) for f in logs])
653
654     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
655         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
656
657     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
658
659     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
660     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
661     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
662
663     file_output(CAP_YUM, [YUM_LOG])
664     tree_output(CAP_YUM, YUM_REPOS_DIR)
665     cmd_output(CAP_YUM, [RPM, '-qa'])
666     file_output(CAP_YUM, [APT_SOURCES_LIST])
667     tree_output(CAP_YUM, APT_SOURCES_LIST_D)
668     cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
669
670     # Filter out ovs relevant information if --ovs option passed
671     # else collect all information
672     filters = set()
673     if only_ovs_info:
674         filters.add('ovs')
675         ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
676                          CAP_NETWORK_CONFIG]
677         ovs_info_list = ['process-tree']
678         # We cannot use iteritems, since we modify 'data' as we pass through
679         for (k, v) in data.items():
680             cap = v['cap']
681             if 'filename' in v:
682                 info = k[0]
683             else:
684                 info = k
685             if info not in ovs_info_list and cap not in ovs_info_caps:
686                 del data[k]
687
688     if filters:
689         filter = ",".join(filters)
690     else:
691         filter = None
692
693     try:
694         load_plugins(filter=filter)
695     except:
696         pass
697
698     # permit the user to filter out data
699     # We cannot use iteritems, since we modify 'data' as we pass through
700     for (k, v) in sorted(data.items()):
701        cap = v['cap']
702        if 'filename' in v:
703            key = k[0]
704        else:
705            key = k
706        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
707            del data[k]
708
709     # collect selected data now
710     output_ts('Running commands to collect data')
711     collect_data()
712
713     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
714
715     # include inventory
716     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
717
718     # create archive
719     if output_fd == -1:
720         if output_file is None:
721             dirname = BUG_DIR
722         else:
723             dirname = os.path.dirname(output_file)
724         if dirname and not os.path.exists(dirname):
725             try:
726                 os.makedirs(dirname)
727             except:
728                 pass
729
730     if output_fd == -1:
731         output_ts('Creating output file')
732
733     if output_type.startswith('tar'):
734         make_tar(subdir, output_type, output_fd, output_file)
735     else:
736         make_zip(subdir, output_file)
737
738     clean_tapdisk_logs()
739
740     if dbg:
741         print >>sys.stderr, "Category sizes (max, actual):\n"
742         for c in caps.keys():
743             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
744                                                      cap_sizes[c])
745     return 0
746
747 def find_tapdisk_logs():
748     return glob.glob('/var/log/blktap/*.log*')
749
750 def generate_tapdisk_logs():
751     for pid in pidof('tapdisk'):
752         try:
753             os.kill(pid, SIGUSR1)
754             output_ts("Including logs for tapdisk process %d" % pid)
755         except :
756             pass
757     # give processes a second to write their logs
758     time.sleep(1)
759
760 def clean_tapdisk_logs():
761     for filename in find_tapdisk_logs():
762         try:
763             os.remove(filename)
764         except :
765             pass
766
767 def filter_db_pii(str, state):
768     if 'in_secret_table' not in state:
769         state['in_secret_table'] = False
770
771     if str.startswith('<table ') and 'name="secret"' in str:
772         state['in_secret_table'] = True
773     elif str.startswith('</table>'):
774         state['in_secret_table'] = False
775
776     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
777         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
778     return str
779
780 def dump_scsi_hosts(cap):
781     output = ''
782     l = os.listdir('/sys/class/scsi_host')
783     l.sort()
784
785     for h in l:
786         procname = ''
787         try:
788                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
789                 procname = f.readline().strip("\n")
790                 f.close()
791         except:
792                 pass
793         modelname = None
794         try:
795                 f = open('/sys/class/scsi_host/%s/model_name' % h)
796                 modelname = f.readline().strip("\n")
797                 f.close()
798         except:
799                 pass
800
801         output += "%s:\n" %h
802         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
803
804     return output
805
806 def module_info(cap):
807     output = StringIO.StringIO()
808     modules = open(PROC_MODULES, 'r')
809     procs = []
810
811     for line in modules:
812         module = line.split()[0]
813         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
814     modules.close()
815
816     run_procs([procs])
817
818     return output.getvalue()
819
820
821 def multipathd_topology(cap):
822     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
823                      stdout=PIPE, stderr=dev_null)
824     stdout, stderr = pipe.communicate('show topology')
825
826     return stdout
827
828 def dp_list():
829     output = StringIO.StringIO()
830     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
831
832     run_procs([procs])
833
834     if not procs[0].timed_out:
835         return output.getvalue().splitlines()
836     return []
837
838 def bond_list(pid):
839     output = StringIO.StringIO()
840     procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
841
842     run_procs([procs])
843
844     if not procs[0].timed_out:
845         bonds = output.getvalue().splitlines()[1:]
846         return [x.split('\t')[1] for x in bonds]
847     return []
848
849 def fd_usage(cap):
850     output = ''
851     fd_dict = {}
852     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
853         try:
854             fh = open('/proc/'+d+'/cmdline')
855             name = fh.readline()
856             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
857             if num_fds > 0:
858                 if not num_fds in fd_dict:
859                     fd_dict[num_fds] = []
860                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
861         finally:
862             fh.close()
863     keys = fd_dict.keys()
864     keys.sort(lambda a, b: int(b) - int(a))
865     for k in keys:
866         output += "%s: %s\n" % (k, str(fd_dict[k]))
867     return output
868
869 def dump_rdac_groups(cap):
870     output = StringIO.StringIO()
871     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
872
873     run_procs([procs])
874
875     if not procs[0].timed_out:
876         proc_line = 0
877         for line in output.getvalue().splitlines():
878             if line.startswith('ID'):
879                 proc_line = 2
880             elif line.startswith('----'):
881                 proc_line -= 1
882             elif proc_line > 0:
883                 group, _ = line.split(None, 1)
884                 cmd_output(cap, [MPPUTIL, '-g', group])
885
886 def load_plugins(just_capabilities=False, filter=None):
887     def getText(nodelist):
888         rc = ""
889         for node in nodelist:
890             if node.nodeType == node.TEXT_NODE:
891                 rc += node.data
892         return rc.encode()
893
894     def getBoolAttr(el, attr, default=False):
895         ret = default
896         val = el.getAttribute(attr).lower()
897         if val in ['true', 'false', 'yes', 'no']:
898             ret = val in ['true', 'yes']
899         return ret
900
901     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
902         if not caps.has_key(dir):
903             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
904                 continue
905             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
906             assert xmldoc.documentElement.tagName == "capability"
907
908             pii, min_size, max_size, min_time, max_time, mime = \
909                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
910
911             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
912                 pii = xmldoc.documentElement.getAttribute("pii")
913             if xmldoc.documentElement.getAttribute("min_size") != '':
914                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
915             if xmldoc.documentElement.getAttribute("max_size") != '':
916                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
917             if xmldoc.documentElement.getAttribute("min_time") != '':
918                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
919             if xmldoc.documentElement.getAttribute("max_time") != '':
920                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
921             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
922                 mime = xmldoc.documentElement.getAttribute("mime")
923             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
924             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
925
926             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
927
928         if just_capabilities:
929             continue
930
931         plugdir = os.path.join(PLUGIN_DIR, dir)
932         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
933             xmldoc = parse(os.path.join(plugdir, file))
934             assert xmldoc.documentElement.tagName == "collect"
935
936             for el in xmldoc.documentElement.getElementsByTagName("*"):
937                 filters_tmp = el.getAttribute("filters")
938                 if filters_tmp == '':
939                     filters = []
940                 else:
941                     filters = filters_tmp.split(',')
942                 if not(filter is None or filter in filters):
943                     continue
944                 if el.tagName == "files":
945                     newest_first = getBoolAttr(el, 'newest_first')
946                     file_output(dir, getText(el.childNodes).split(),
947                                 newest_first=newest_first)
948                 elif el.tagName == "directory":
949                     pattern = el.getAttribute("pattern")
950                     if pattern == '': pattern = None
951                     negate = getBoolAttr(el, 'negate')
952                     newest_first = getBoolAttr(el, 'newest_first')
953                     tree_output(dir, getText(el.childNodes),
954                                 pattern and re.compile(pattern) or None,
955                                 negate=negate, newest_first=newest_first)
956                 elif el.tagName == "command":
957                     label = el.getAttribute("label")
958                     if label == '': label = None
959                     cmd_output(dir, getText(el.childNodes), label)
960
961 def make_tar(subdir, suffix, output_fd, output_file):
962     global SILENT_MODE, data
963
964     mode = 'w'
965     if suffix == 'tar.bz2':
966         mode = 'w:bz2'
967     elif suffix == 'tar.gz':
968         mode = 'w:gz'
969
970     if output_fd == -1:
971         if output_file is None:
972             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
973         else:
974             filename = output_file
975         old_umask = os.umask(0077)
976         tf = tarfile.open(filename, mode)
977         os.umask(old_umask)
978     else:
979         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
980
981     try:
982         for (k, v) in data.items():
983             try:
984                 tar_filename = os.path.join(subdir, construct_filename(k, v))
985                 ti = tarfile.TarInfo(tar_filename)
986
987                 ti.uname = 'root'
988                 ti.gname = 'root'
989
990                 if v.has_key('output'):
991                     ti.mtime = v['output'].mtime
992                     ti.size = len(v['output'].getvalue())
993                     v['output'].seek(0)
994                     tf.addfile(ti, v['output'])
995                 elif v.has_key('filename'):
996                     s = os.stat(v['filename'])
997                     ti.mtime = s.st_mtime
998                     ti.size = s.st_size
999                     tf.addfile(ti, file(v['filename']))
1000             except:
1001                 pass
1002     finally:
1003         tf.close()
1004
1005     if output_fd == -1:
1006         output ('Writing tarball %s successful.' % filename)
1007         if SILENT_MODE:
1008             print filename
1009
1010
1011 def make_zip(subdir, output_file):
1012     global SILENT_MODE, data
1013
1014     if output_file is None:
1015         filename = "%s/%s.zip" % (BUG_DIR, subdir)
1016     else:
1017         filename = output_file
1018     old_umask = os.umask(0077)
1019     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1020     os.umask(old_umask)
1021
1022     try:
1023         for (k, v) in data.items():
1024             try:
1025                 dest = os.path.join(subdir, construct_filename(k, v))
1026
1027                 if v.has_key('output'):
1028                     zf.writestr(dest, v['output'].getvalue())
1029                 else:
1030                     if os.stat(v['filename']).st_size < 50:
1031                         compress_type = zipfile.ZIP_STORED
1032                     else:
1033                         compress_type = zipfile.ZIP_DEFLATED
1034                     zf.write(v['filename'], dest, compress_type)
1035             except:
1036                 pass
1037     finally:
1038         zf.close()
1039
1040     output ('Writing archive %s successful.' % filename)
1041     if SILENT_MODE:
1042         print filename
1043
1044
1045 def make_inventory(inventory, subdir):
1046     document = getDOMImplementation().createDocument(
1047         None, INVENTORY_XML_ROOT, None)
1048
1049     # create summary entry
1050     s = document.createElement(INVENTORY_XML_SUMMARY)
1051     user = os.getenv('SUDO_USER', os.getenv('USER'))
1052     if user:
1053         s.setAttribute('user', user)
1054     s.setAttribute('date', time.strftime('%c'))
1055     s.setAttribute('hostname', platform.node())
1056     s.setAttribute('uname', ' '.join(platform.uname()))
1057     s.setAttribute('uptime', commands.getoutput(UPTIME))
1058     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1059
1060     map(lambda (k, v): inventory_entry(document, subdir, k, v),
1061         inventory.items())
1062     return document.toprettyxml()
1063
1064 def inventory_entry(document, subdir, k, v):
1065     try:
1066         el = document.createElement(INVENTORY_XML_ELEMENT)
1067         el.setAttribute('capability', v['cap'])
1068         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1069         el.setAttribute('md5sum', md5sum(v))
1070         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1071     except:
1072         pass
1073
1074
1075 def md5sum(d):
1076     m = md5.new()
1077     if d.has_key('filename'):
1078         f = open(d['filename'])
1079         data = f.read(1024)
1080         while len(data) > 0:
1081             m.update(data)
1082             data = f.read(1024)
1083         f.close()
1084     elif d.has_key('output'):
1085         m.update(d['output'].getvalue())
1086     return m.hexdigest()
1087
1088
1089 def construct_filename(k, v):
1090     if v.has_key('filename'):
1091         if v['filename'][0] == '/':
1092             return v['filename'][1:]
1093         else:
1094             return v['filename']
1095     s = k.replace(' ', '-')
1096     s = s.replace('--', '-')
1097     s = s.replace('/', '%')
1098     if s.find('.') == -1:
1099         s += '.out'
1100
1101     return s
1102
1103 def update_capabilities():
1104     pass
1105
1106 def update_cap_size(cap, size):
1107     update_cap(cap, MIN_SIZE, size)
1108     update_cap(cap, MAX_SIZE, size)
1109     update_cap(cap, CHECKED, size > 0)
1110
1111
1112 def update_cap(cap, k, v):
1113     global caps
1114     l = list(caps[cap])
1115     l[k] = v
1116     caps[cap] = tuple(l)
1117
1118
1119 def size_of_dir(d, pattern=None, negate=False):
1120     if os.path.isdir(d):
1121         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1122                            pattern, negate)
1123     else:
1124         return 0
1125
1126
1127 def size_of_all(files, pattern=None, negate=False):
1128     return sum([size_of(f, pattern, negate) for f in files])
1129
1130
1131 def matches(f, pattern, negate):
1132     if negate:
1133         return not matches(f, pattern, False)
1134     else:
1135         return pattern is None or pattern.match(f)
1136
1137
1138 def size_of(f, pattern, negate):
1139     if os.path.isfile(f) and matches(f, pattern, negate):
1140         return os.stat(f)[6]
1141     else:
1142         return size_of_dir(f, pattern, negate)
1143
1144
1145 def print_capabilities():
1146     document = getDOMImplementation().createDocument(
1147         "ns", CAP_XML_ROOT, None)
1148     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1149     print document.toprettyxml()
1150
1151 def capability(document, key):
1152     c = caps[key]
1153     el = document.createElement(CAP_XML_ELEMENT)
1154     el.setAttribute('key', c[KEY])
1155     el.setAttribute('pii', c[PII])
1156     el.setAttribute('min-size', str(c[MIN_SIZE]))
1157     el.setAttribute('max-size', str(c[MAX_SIZE]))
1158     el.setAttribute('min-time', str(c[MIN_TIME]))
1159     el.setAttribute('max-time', str(c[MAX_TIME]))
1160     el.setAttribute('content-type', c[MIME])
1161     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1162     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1163
1164
1165 def prettyDict(d):
1166     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1167     return '\n'.join([format % i for i in d.items()]) + '\n'
1168
1169
1170 def yes(prompt):
1171     yn = raw_input(prompt)
1172
1173     return len(yn) == 0 or yn.lower()[0] == 'y'
1174
1175
1176 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1177
1178 def disk_list():
1179     disks = []
1180     try:
1181         f = open('/proc/partitions')
1182         f.readline()
1183         f.readline()
1184         for line in f.readlines():
1185             (major, minor, blocks, name) = line.split()
1186             if int(major) < 254 and not partition_re.match(name):
1187                 disks.append(name)
1188         f.close()
1189     except:
1190         pass
1191     return disks
1192
1193
1194 class ProcOutput:
1195     debug = False
1196
1197     def __init__(self, command, max_time, inst=None, filter=None):
1198         self.command = command
1199         self.max_time = max_time
1200         self.inst = inst
1201         self.running = False
1202         self.status = None
1203         self.timed_out = False
1204         self.failed = False
1205         self.timeout = int(time.time()) + self.max_time
1206         self.filter = filter
1207         self.filter_state = {}
1208
1209     def __del__(self):
1210         self.terminate()
1211
1212     def cmdAsStr(self):
1213         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1214
1215     def run(self):
1216         self.timed_out = False
1217         try:
1218             if ProcOutput.debug:
1219                 output_ts("Starting '%s'" % self.cmdAsStr())
1220             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1221             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1222             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1223             self.running = True
1224             self.failed = False
1225         except:
1226             output_ts("'%s' failed" % self.cmdAsStr())
1227             self.running = False
1228             self.failed = True
1229
1230     def terminate(self):
1231         if self.running:
1232             try:
1233                 self.proc.stdout.close()
1234                 os.kill(self.proc.pid, SIGTERM)
1235             except:
1236                 pass
1237             self.proc = None
1238             self.running = False
1239             self.status = SIGTERM
1240
1241     def read_line(self):
1242         assert self.running
1243         line = self.proc.stdout.readline()
1244         if line == '':
1245             # process exited
1246             self.proc.stdout.close()
1247             self.status = self.proc.wait()
1248             self.proc = None
1249             self.running = False
1250         else:
1251             if self.filter:
1252                 line = self.filter(line, self.filter_state)
1253             if self.inst:
1254                 self.inst.write(line)
1255
1256 def run_procs(procs):
1257     while True:
1258         pipes = []
1259         active_procs = []
1260
1261         for pp in procs:
1262             for p in pp:
1263                 if p.running:
1264                     active_procs.append(p)
1265                     pipes.append(p.proc.stdout)
1266                     break
1267                 elif p.status == None and not p.failed and not p.timed_out:
1268                     p.run()
1269                     if p.running:
1270                         active_procs.append(p)
1271                         pipes.append(p.proc.stdout)
1272                         break
1273
1274         if len(pipes) == 0:
1275             # all finished
1276             break
1277
1278         (i, o, x) = select(pipes, [], [], 1.0)
1279         now = int(time.time())
1280
1281         # handle process output
1282         for p in active_procs:
1283             if p.proc.stdout in i:
1284                 p.read_line()
1285
1286             # handle timeout
1287             if p.running and now > p.timeout:
1288                 output_ts("'%s' timed out" % p.cmdAsStr())
1289                 if p.inst:
1290                     p.inst.write("\n** timeout **\n")
1291                 p.timed_out = True
1292                 p.terminate()
1293
1294
1295 def pidof(name):
1296     pids = []
1297
1298     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1299         try:
1300             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1301                 pids.append(int(d))
1302         except:
1303             pass
1304
1305     return pids
1306
1307
1308 class StringIOmtime(StringIO.StringIO):
1309     def __init__(self, buf=''):
1310         StringIO.StringIO.__init__(self, buf)
1311         self.mtime = time.time()
1312
1313     def write(self, s):
1314         StringIO.StringIO.write(self, s)
1315         self.mtime = time.time()
1316
1317
1318 if __name__ == "__main__":
1319     try:
1320         sys.exit(main())
1321     except KeyboardInterrupt:
1322         print "\nInterrupted."
1323         sys.exit(3)