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