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