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