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