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