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