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